package com.wusuowei.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wusuowei.common.config.WuSuoWeiConfig;
import com.wusuowei.common.constant.CacheConstants;
import com.wusuowei.common.core.RedisCache;
import com.wusuowei.gateway.utils.R;
import com.wusuowei.gateway.utils.RespEnum;
import com.wusuowei.gateway.utils.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;


/**
 * @description 网关身份验证筛选器
 * @author LGY
 * @date 2023/04/10 21:44
 * @version 1.0.0
 */
@RefreshScope
@Component
@Slf4j
@Order(0)
public class GatewayAuthFilter implements GlobalFilter {

    @Autowired
    private WuSuoWeiConfig wuSuoWeiConfig;

    //白名单
    private static List<String> whitelist = null;

    static {
        //加载白名单
        try (
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist = new ArrayList<>(strings);

        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}", e.getMessage());
            e.printStackTrace();
        }

    }

    @Autowired
    TokenStore tokenStore;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest req = exchange.getRequest();
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            return chain.filter(exchange);
        }
        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                return chain.filter(exchange);
            }
        }

        //检查token是否存在
        String token = getToken(exchange);
        if (StringUtils.isBlank(token)) {
            return responseError(exchange, RespEnum.UNAUTHENTICATEDERROR, null);
        }
        //判断是否是有效的token
        OAuth2AccessToken oAuth2AccessToken;
        String uid = null;

        try {
            oAuth2AccessToken = tokenStore.readAccessToken(token);
            uid = (String) oAuth2AccessToken.getAdditionalInformation().get("uid");
        } catch (Exception e) {
            log.info("认证令牌无效: {}", token);
            return responseError(exchange, RespEnum.TOKEN_ILLEGAL,uid);
        }

        //判断是否多次登录，多个浏览器的情况
        RedisCache redisCache = SpringContextUtils.getBean(RedisCache.class);
        String accessToken = redisCache.getCacheObject(CacheConstants.ACCESS_TOKEN_KEY+uid);
        if(!token.equals(accessToken)){
            return responseError(exchange, RespEnum.TOKEN_EXPIRATION, uid);
        }
        boolean expired = oAuth2AccessToken.isExpired();

        //token续期  如果请求中没有refreshToken，直接返回过期，否则续期
        if (expired) {
            String refreshToken = exchange.getRequest().getHeaders().get("refresh_token").get(0);
            if(StringUtils.isNotBlank(refreshToken)){
                ServerHttpResponse response = exchange.getResponse();
                OAuth2AccessToken newToken = null;
                //生成新token
                try {
                    newToken = getNewToken(refreshToken);
                    redisCache.setCacheObject(CacheConstants.ACCESS_TOKEN_KEY+uid,newToken.getValue(),wuSuoWeiConfig.getRefreshTokenexpire(), TimeUnit.SECONDS);
                } catch (Exception e) {
                    return responseError(exchange, RespEnum.REFRESH_TOKEN_EXPIRATION, uid);
                }
                log.info("token续期成功:" + newToken.getValue());
                response.getHeaders().add("refreshToken", String.valueOf(newToken.getRefreshToken()));
                response.getHeaders().add("accessToken", newToken.getValue());
                response.getHeaders().add("Access-Control-Expose-Headers", "refreshToken");
                response.getHeaders().add("Access-Control-Expose-Headers", "accessToken");
                return response.setComplete();
            }else{
                return responseError(exchange, RespEnum.TOKEN_EXPIRATION, uid);
            }
        }

        return chain.filter(exchange);

    }

    /**
     * 将非法请求返回
     * @return
     */
    private Mono<Void> responseError(ServerWebExchange exchange, RespEnum respEnum, String uid) {
        if(uid!=null){
            RedisCache redisCache = SpringContextUtils.getBean(RedisCache.class);
            redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY+uid);
        }
        ServerHttpResponse response = exchange.getResponse();
        // 设置响应头
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        // 设置响应数据
        R responseData = R.error(respEnum.getCode(), respEnum.getMessage());
        byte[] bytes = new byte[0];
        try {
            bytes = new ObjectMapper().writeValueAsBytes(responseData);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        // 将响应对象写入响应输出流
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }

    public OAuth2AccessToken getNewToken(String refreshToken) {
        MultiValueMap<String, Object> paramsMap = new LinkedMultiValueMap<>();
        paramsMap.set("grant_type", "refresh_token");
        paramsMap.set("refresh_token", refreshToken);
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor("client", "112233"));
        OAuth2AccessToken token = restTemplate.postForObject("http://localhost:8160/auth/oauth/token", paramsMap, OAuth2AccessToken.class);
        return token;
    }
}
